#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""代码折叠组件"""

import logging
from PyQt5.QtWidgets import QWidget
from PyQt5.QtCore import Qt, QRect, QSize
from PyQt5.QtGui import QPainter, QColor, QFont, QFontMetrics, QPointF


class CodeFoldingPanel(QWidget):
    """代码折叠面板组件"""
    
    def __init__(self, editor=None, parent=None):
        """初始化代码折叠面板
        
        Args:
            editor: 编辑器控件
            parent: 父控件
        """
        super().__init__(parent)
        
        # 日志配置
        self.logger = logging.getLogger("CodeFoldingPanel")
        
        # 编辑器引用
        self.editor = editor
        
        # 折叠区域信息
        self.fold_regions = {}
        
        # 折叠状态
        self.folded_lines = set()
        
        # 面板宽度
        self.panel_width = 20
        
        # 设置面板属性
        self.setFixedWidth(self.panel_width)
        self.setMouseTracking(True)
        
        # 缓存的折叠图标区域
        self.fold_icon_regions = {}
        
        # 当前鼠标悬停的折叠图标
        self.hovered_fold_icon = None
        
        # 配置样式
        self._configure_style()
    
    def _configure_style(self):
        """配置样式"""
        # 颜色配置
        self.colors = {
            "background": QColor(230, 230, 230),
            "foreground": QColor(100, 100, 100),
            "hover_background": QColor(200, 200, 200),
            "folded_background": QColor(210, 210, 210),
            "fold_line": QColor(160, 160, 160),
            "unfolded_triangle": QColor(100, 100, 100),
            "folded_triangle": QColor(100, 100, 100),
        }
        
        # 折叠图标大小
        self.icon_size = 10
        
        # 折叠线宽度
        self.line_width = 1
    
    def set_editor(self, editor):
        """设置编辑器
        
        Args:
            editor: 编辑器控件
        """
        self.editor = editor
        
        # 如果编辑器存在，连接信号
        if self.editor:
            # 连接文本变更信号
            self.editor.textChanged.connect(self.on_text_changed)
            
            # 连接滚动信号
            self.editor.verticalScrollBar().valueChanged.connect(self.update)
            self.editor.horizontalScrollBar().valueChanged.connect(self.update)
    
    def on_text_changed(self):
        """文本变更时的处理"""
        # 重新计算折叠区域
        self._update_fold_regions()
        
        # 更新面板显示
        self.update()
    
    def _update_fold_regions(self):
        """更新折叠区域信息"""
        if not self.editor:
            return
        
        # 清空折叠区域
        self.fold_regions.clear()
        
        # 获取文档内容
        document = self.editor.document()
        
        # 初始化缩进栈
        indent_stack = []
        
        # 遍历每一行
        for block_number in range(document.blockCount()):
            block = document.findBlockByNumber(block_number)
            
            # 获取行文本
            line_text = block.text().strip()
            
            # 跳过空行
            if not line_text:
                continue
            
            # 获取缩进级别
            indent_level = self._get_indent_level(block.text())
            
            # 检查是否是可折叠的开始行
            if self._is_foldable_start(line_text):
                # 记录折叠开始行和缩进级别
                indent_stack.append((block_number, indent_level))
            
            # 检查是否是可折叠的结束行
            if self._is_foldable_end(line_text):
                # 查找匹配的开始行
                while indent_stack:
                    start_line, start_indent = indent_stack.pop()
                    
                    # 找到匹配的缩进级别
                    if start_indent < indent_level:
                        # 记录折叠区域
                        self.fold_regions[start_line] = {
                            "end_line": block_number,
                            "indent_level": start_indent,
                            "folded": start_line in self.folded_lines
                        }
                        
                        # 重新压回栈中（如果有多个结束行）
                        if line_text.endswith(":"):
                            indent_stack.append((block_number, indent_level))
                        
                        break
        
        # 处理未匹配的开始行
        while indent_stack:
            start_line, start_indent = indent_stack.pop()
            
            # 设置结束行为文档的最后一行
            self.fold_regions[start_line] = {
                "end_line": document.blockCount() - 1,
                "indent_level": start_indent,
                "folded": start_line in self.folded_lines
            }
    
    def _get_indent_level(self, text):
        """获取缩进级别
        
        Args:
            text: 文本
            
        Returns:
            int: 缩进级别
        """
        # 计算空格数
        space_count = len(text) - len(text.lstrip(' '))
        
        # 计算制表符数
        tab_count = len(text) - len(text.lstrip('\t'))
        
        # 如果编辑器设置了用空格代替制表符，计算缩进级别
        if hasattr(self.editor, 'use_spaces_for_tabs') and self.editor.use_spaces_for_tabs:
            # 获取标签大小
            tab_size = getattr(self.editor, 'tab_size', 4)
            
            # 计算缩进级别
            return (space_count + tab_count * tab_size) // tab_size
        
        # 默认使用制表符数作为缩进级别
        return tab_count + space_count // 4  # 假设4个空格等于1个缩进级别
    
    def _is_foldable_start(self, text):
        """检查是否是可折叠的开始行
        
        Args:
            text: 文本
            
        Returns:
            bool: 是否是可折叠的开始行
        """
        # Python代码的折叠规则
        # 检查是否以冒号结尾（表示代码块开始）
        if text.endswith(":"):
            # 排除单行注释中的冒号
            if not text.strip().startswith('#'):
                # 排除某些特殊情况（如切片操作中的冒号）
                if not any(op in text for op in ['[', ']', '(', ')', '=', '+', '-', '*', '/', '%']):
                    return True
        
        # 多行字符串的开始
        if text.startswith(('"""', "''"')):
            return True
        
        return False
    
    def _is_foldable_end(self, text):
        """检查是否是可折叠的结束行
        
        Args:
            text: 文本
            
        Returns:
            bool: 是否是可折叠的结束行
        """
        # 多行字符串的结束
        if text.endswith(('"""', "''"')):
            # 排除单行字符串
            if len(text) > 3:
                return True
        
        # 其他情况由缩进级别处理
        return False
    
    def fold_line(self, line_number):
        """折叠指定行
        
        Args:
            line_number: 行号
        """
        # 检查行是否可折叠
        if line_number not in self.fold_regions:
            return False
        
        # 标记行为折叠状态
        self.folded_lines.add(line_number)
        self.fold_regions[line_number]["folded"] = True
        
        # 更新编辑器显示
        self._update_editor_display()
        
        return True
    
    def unfold_line(self, line_number):
        """展开指定行
        
        Args:
            line_number: 行号
        """
        # 检查行是否可折叠
        if line_number not in self.fold_regions:
            return False
        
        # 移除折叠状态
        if line_number in self.folded_lines:
            self.folded_lines.remove(line_number)
            self.fold_regions[line_number]["folded"] = False
        
        # 更新编辑器显示
        self._update_editor_display()
        
        return True
    
    def toggle_line(self, line_number):
        """切换行的折叠状态
        
        Args:
            line_number: 行号
        """
        # 检查行是否可折叠
        if line_number not in self.fold_regions:
            return False
        
        # 切换折叠状态
        if line_number in self.folded_lines:
            self.unfold_line(line_number)
        else:
            self.fold_line(line_number)
        
        return True
    
    def _update_editor_display(self):
        """更新编辑器显示"""
        if not self.editor:
            return
        
        # 通知编辑器更新
        self.editor.update()
    
    def is_line_folded(self, line_number):
        """检查行是否折叠
        
        Args:
            line_number: 行号
            
        Returns:
            bool: 是否折叠
        """
        return line_number in self.folded_lines
    
    def get_visible_line_number(self, logical_line_number):
        """获取可见的行号
        
        Args:
            logical_line_number: 逻辑行号
            
        Returns:
            int: 可见行号
        """
        if not self.editor:
            return logical_line_number
        
        # 计算折叠的行数
        folded_lines_count = 0
        
        for start_line, region in self.fold_regions.items():
            # 如果区域在当前行之前并且是折叠的
            if region["folded"] and start_line < logical_line_number:
                # 计算区域包含的行数
                region_lines = region["end_line"] - start_line
                
                # 如果当前行在区域内部，返回起始行
                if logical_line_number <= region["end_line"]:
                    return start_line - folded_lines_count
                
                # 否则累加折叠的行数
                folded_lines_count += region_lines
        
        # 返回调整后的行号
        return logical_line_number - folded_lines_count
    
    def get_logical_line_number(self, visible_line_number):
        """获取逻辑行号
        
        Args:
            visible_line_number: 可见行号
            
        Returns:
            int: 逻辑行号
        """
        if not self.editor:
            return visible_line_number
        
        # 计算展开的行数
        unfolded_lines_count = 0
        current_visible_line = 0
        
        for start_line, region in sorted(self.fold_regions.items()):
            # 计算起始行的可见行号
            start_visible_line = start_line - unfolded_lines_count
            
            # 如果当前可见行小于起始行的可见行号，说明在当前区域之前
            if visible_line_number < start_visible_line:
                return visible_line_number + unfolded_lines_count
            
            # 如果当前可见行等于起始行的可见行号
            if visible_line_number == start_visible_line:
                # 如果区域是折叠的，返回起始行
                if region["folded"]:
                    return start_line
                # 否则继续
                else:
                    current_visible_line = start_visible_line
                    continue
            
            # 如果区域是折叠的
            if region["folded"]:
                # 累加折叠的行数
                unfolded_lines_count += region["end_line"] - start_line
                
                # 如果当前可见行大于起始行的可见行号，说明在当前区域之后
                if visible_line_number > start_visible_line:
                    continue
            else:
                # 区域是展开的，检查当前可见行是否在区域内
                end_visible_line = start_visible_line + (region["end_line"] - start_line)
                
                if visible_line_number <= end_visible_line:
                    # 返回对应的逻辑行号
                    return start_line + (visible_line_number - start_visible_line)
                
                # 更新当前可见行
                current_visible_line = end_visible_line
        
        # 如果没有找到匹配的区域，返回调整后的行号
        return visible_line_number + unfolded_lines_count
    
    def paintEvent(self, event):
        """绘制事件
        
        Args:
            event: 事件对象
        """
        if not self.editor:
            return
        
        # 创建 painter
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        
        # 绘制背景
        painter.fillRect(event.rect(), self.colors["background"])
        
        # 获取可见区域
        visible_rect = self.editor.visibleRegion().boundingRect()
        
        # 获取第一个和最后一个可见块
        first_block = self.editor.document().findBlockByGeometry(visible_rect).blockNumber()
        last_block = self.editor.document().findBlockByGeometry(
            QRect(visible_rect.left(), visible_rect.bottom(), 1, 1)).blockNumber()
        
        # 调整最后一个可见块
        if last_block < 0:
            last_block = self.editor.document().blockCount() - 1
        
        # 清空折叠图标区域缓存
        self.fold_icon_regions.clear()
        
        # 绘制折叠图标和连接线
        for block_number in range(first_block, last_block + 1):
            # 检查是否是可折叠的行
            if block_number in self.fold_regions:
                # 获取行的矩形区域
                block = self.editor.document().findBlockByNumber(block_number)
                block_rect = self.editor.blockBoundingGeometry(block).translated(self.editor.contentOffset())
                
                # 计算折叠图标的位置
                icon_x = (self.width() - self.icon_size) // 2
                icon_y = block_rect.top() + (block_rect.height() - self.icon_size) // 2
                
                # 保存折叠图标的区域
                icon_rect = QRect(icon_x, icon_y, self.icon_size, self.icon_size)
                self.fold_icon_regions[block_number] = icon_rect
                
                # 检查鼠标是否悬停在折叠图标上
                if block_number == self.hovered_fold_icon:
                    # 绘制悬停背景
                    hover_rect = QRect(0, icon_y - 2, self.width(), self.icon_size + 4)
                    painter.fillRect(hover_rect, self.colors["hover_background"])
                
                # 检查行是否折叠
                if block_number in self.folded_lines:
                    # 绘制折叠的三角形
                    self._draw_folded_triangle(painter, icon_rect)
                    
                    # 绘制折叠的背景
                    folded_rect = QRect(
                        0, 
                        block_rect.top(), 
                        self.width(), 
                        block_rect.height()
                    )
                    painter.fillRect(folded_rect, self.colors["folded_background"])
                else:
                    # 绘制展开的三角形
                    self._draw_unfolded_triangle(painter, icon_rect)
                
                # 绘制连接线
                self._draw_connection_lines(painter, block_number, block_rect)
    
    def _draw_folded_triangle(self, painter, rect):
        """绘制折叠的三角形
        
        Args:
            painter: 绘图对象
            rect: 矩形区域
        """
        # 设置画笔颜色
        painter.setPen(self.colors["folded_triangle"])
        painter.setBrush(self.colors["folded_triangle"])
        
        # 计算三角形的三个点
        x_center = rect.center().x()
        y_center = rect.center().y()
        size = min(rect.width(), rect.height()) // 2
        
        points = [
            (x_center - size, y_center - size),
            (x_center + size, y_center),
            (x_center - size, y_center + size)
        ]
        
        # 绘制三角形
        qpoints = [QPointF(x, y) for x, y in points]
        painter.drawPolygon(*qpoints)
    
    def _draw_unfolded_triangle(self, painter, rect):
        """绘制展开的三角形
        
        Args:
            painter: 绘图对象
            rect: 矩形区域
        """
        # 设置画笔颜色
        painter.setPen(self.colors["unfolded_triangle"])
        painter.setBrush(self.colors["unfolded_triangle"])
        
        # 计算三角形的三个点
        x_center = rect.center().x()
        y_center = rect.center().y()
        size = min(rect.width(), rect.height()) // 2
        
        points = [
            (x_center - size, y_center - size),
            (x_center + size, y_center - size),
            (x_center, y_center + size)
        ]
        
        # 绘制三角形
        qpoints = [QPointF(x, y) for x, y in points]
        painter.drawPolygon(*qpoints)
    
    def _draw_connection_lines(self, painter, block_number, block_rect):
        """绘制连接线
        
        Args:
            painter: 绘图对象
            block_number: 块号
            block_rect: 块的矩形区域
        """
        if block_number not in self.fold_regions:
            return
        
        # 获取折叠区域信息
        region = self.fold_regions[block_number]
        
        # 设置画笔
        painter.setPen(self.colors["fold_line"])
        painter.setBrush(Qt.NoBrush)
        
        # 计算线条的起始和结束位置
        x_center = self.width() // 2
        line_start_y = block_rect.bottom()
        
        # 检查下一行是否在当前区域内
        next_block_number = block_number + 1
        while next_block_number <= region["end_line"]:
            # 检查下一行是否是另一个折叠区域的开始
            if next_block_number in self.fold_regions:
                next_region = self.fold_regions[next_block_number]
                
                # 如果下一行的缩进级别大于当前行的缩进级别
                if next_region["indent_level"] > region["indent_level"]:
                    # 绘制垂直线
                    next_block = self.editor.document().findBlockByNumber(next_block_number)
                    next_block_rect = self.editor.blockBoundingGeometry(next_block).translated(self.editor.contentOffset())
                    
                    painter.drawLine(x_center, line_start_y, x_center, next_block_rect.top())
                    
                    # 绘制水平线
                    painter.drawLine(x_center, next_block_rect.top(), self.width() // 2 + 5, next_block_rect.top())
                    
                    # 更新起始位置
                    line_start_y = next_block_rect.bottom()
                
                # 跳出循环
                break
            
            # 移动到下一行
            next_block_number += 1
    
    def mousePressEvent(self, event):
        """鼠标按下事件
        
        Args:
            event: 事件对象
        """
        # 检查是否点击了折叠图标
        for line_number, rect in self.fold_icon_regions.items():
            if rect.contains(event.pos()):
                # 切换行的折叠状态
                self.toggle_line(line_number)
                
                # 更新面板显示
                self.update()
                
                break
    
    def mouseMoveEvent(self, event):
        """鼠标移动事件
        
        Args:
            event: 事件对象
        """
        # 检查鼠标是否悬停在折叠图标上
        hovered_line = None
        for line_number, rect in self.fold_icon_regions.items():
            if rect.contains(event.pos()):
                hovered_line = line_number
                break
        
        # 如果悬停的图标发生变化，更新显示
        if hovered_line != self.hovered_fold_icon:
            self.hovered_fold_icon = hovered_line
            
            # 更新鼠标指针
            if hovered_line is not None:
                self.setCursor(Qt.PointingHandCursor)
            else:
                self.setCursor(Qt.ArrowCursor)
            
            # 更新面板显示
            self.update()
    
    def leaveEvent(self, event):
        """鼠标离开事件
        
        Args:
            event: 事件对象
        """
        # 重置悬停状态
        if self.hovered_fold_icon is not None:
            self.hovered_fold_icon = None
            
            # 重置鼠标指针
            self.setCursor(Qt.ArrowCursor)
            
            # 更新面板显示
            self.update()
    
    def set_theme(self, theme_name):
        """设置主题
        
        Args:
            theme_name: 主题名称
        """
        try:
            # 主题定义
            themes = {
                "default": {
                    "background": QColor(230, 230, 230),
                    "foreground": QColor(100, 100, 100),
                    "hover_background": QColor(200, 200, 200),
                    "folded_background": QColor(210, 210, 210),
                    "fold_line": QColor(160, 160, 160),
                    "unfolded_triangle": QColor(100, 100, 100),
                    "folded_triangle": QColor(100, 100, 100),
                },
                "dark": {
                    "background": QColor(50, 50, 50),
                    "foreground": QColor(200, 200, 200),
                    "hover_background": QColor(70, 70, 70),
                    "folded_background": QColor(60, 60, 60),
                    "fold_line": QColor(80, 80, 80),
                    "unfolded_triangle": QColor(150, 150, 150),
                    "folded_triangle": QColor(150, 150, 150),
                }
            }
            
            # 检查主题是否存在
            if theme_name not in themes:
                self.logger.warning(f"未知的主题: {theme_name}")
                return
            
            # 更新颜色配置
            self.colors = themes[theme_name]
            
            # 更新面板显示
            self.update()
            
            self.logger.info(f"已设置代码折叠面板主题: {theme_name}")
        except Exception as e:
            self.logger.error(f"设置主题失败: {str(e)}")
    
    def set_panel_width(self, width):
        """设置面板宽度
        
        Args:
            width: 面板宽度
        """
        self.panel_width = max(10, width)
        self.setFixedWidth(self.panel_width)
        
        # 更新面板显示
        self.update()
    
    def set_icon_size(self, size):
        """设置折叠图标大小
        
        Args:
            size: 图标大小
        """
        self.icon_size = max(5, size)
        
        # 更新面板显示
        self.update()
    
    def set_line_width(self, width):
        """设置折叠线宽度
        
        Args:
            width: 线宽度
        """
        self.line_width = max(1, width)
        
        # 更新面板显示
        self.update()
    
    def get_folded_lines(self):
        """获取所有折叠的行
        
        Returns:
            set: 折叠的行号集合
        """
        return self.folded_lines.copy()
    
    def set_folded_lines(self, folded_lines):
        """设置折叠的行
        
        Args:
            folded_lines: 折叠的行号集合
        """
        self.folded_lines = set(folded_lines)
        
        # 更新折叠区域的状态
        for line_number in self.fold_regions:
            self.fold_regions[line_number]["folded"] = line_number in self.folded_lines
        
        # 更新编辑器显示
        self._update_editor_display()
    
    def clear_all_folds(self):
        """清除所有折叠
        
        Returns:
            bool: 是否成功
        """
        # 清除折叠状态
        self.folded_lines.clear()
        
        # 更新折叠区域的状态
        for line_number in self.fold_regions:
            self.fold_regions[line_number]["folded"] = False
        
        # 更新编辑器显示
        self._update_editor_display()
        
        return True
    
    def fold_all(self):
        """折叠所有可折叠的行
        
        Returns:
            bool: 是否成功
        """
        # 设置所有可折叠的行为折叠状态
        for line_number in self.fold_regions:
            self.folded_lines.add(line_number)
            self.fold_regions[line_number]["folded"] = True
        
        # 更新编辑器显示
        self._update_editor_display()
        
        return True
    
    def save_fold_state(self):
        """保存折叠状态
        
        Returns:
            list: 折叠的行号列表
        """
        return list(self.folded_lines)
    
    def load_fold_state(self, folded_lines):
        """加载折叠状态
        
        Args:
            folded_lines: 折叠的行号列表
        """
        self.set_folded_lines(folded_lines)